#pragma once

#include <boost/range/concepts.hpp>
#include <vex/iterable/range_traits.h>
#include <vex/iterable/concept/range_concepts.h>
#include <vex.tests/mstest/crt_mem_check.h>
#include <vex.tests/mstest/log_message.h>
#include <type_traits>
#include <limits>
#include <cstddef>

#include "CppUnitTest.h"

namespace tests {

    using namespace Microsoft::VisualStudio::CppUnitTestFramework;
    namespace vi = vex::iterable;

    struct trivial_range {
        template<class TExpRange, class TRange>
        static void has_expected_type_and_size(TRange &&, std::size_t p_exp_size)
        {
            typedef typename std::decay<TRange>::type range_type;
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::TrivialRange<range_type>));
            Assert::IsTrue(std::is_same<TExpRange, range_type>::value, wlog_message()
                << "expected: " << typeid(TExpRange).name() << std::endl
                << "got: " << typeid(range_type).name() | as_string);

            Assert::IsTrue(p_exp_size <= sizeof(TRange), wlog_message()
                << "expected: " << p_exp_size << std::endl
                << "got: " << sizeof(TRange) | as_string);
        }

        template<class TExpCat, class TExpRef, class TRange>
        static void has_expected_traits(TRange && p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::TrivialRange<TRange>));
            typedef vex::iterable::range_traits<TRange> traits;
            Assert::IsTrue(std::is_same<TExpCat, typename traits::category>::value);
            Assert::IsTrue(std::is_same<TExpRef, typename traits::reference>::value);
        }

        template<class TRange>
        static void move_front_iteration_succeeds_expected_number_of_times(TRange && p_range, std::size_t p_count)
        {
            std::size_t t_count = 0;
            while (p_range.move_front()) {
                ++t_count;
            }
            Assert::AreEqual(p_count, t_count);
        }
    };

    struct output_range : public trivial_range {
        template<class TSourceRange, class TTargetRange>
        static bool copy(TSourceRange && p_source, TTargetRange && p_target)
        {
            typedef vex::iterable::range_traits<TSourceRange> source_traits;
            typedef vex::iterable::range_traits<TTargetRange> target_traits;
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::OnePassRange<typename source_traits::range_type>));
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::OutputRange<typename target_traits::range_type, typename source_traits::value_type>));

            bool t_copy_success = true;
            while (t_copy_success && p_source.move_front()) {
                t_copy_success = p_target.put(p_source.front());
            }

            return t_copy_success;
        }

        template<class TSourceRange, class TTargetRange>
        static bool copy_n(TSourceRange && p_source, TTargetRange && p_target, std::size_t n)
        {
            typedef vex::iterable::range_traits<TSourceRange> source_traits;
            typedef vex::iterable::range_traits<TTargetRange> target_traits;
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::OnePassRange<typename source_traits::range_type>));
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::OutputRange<typename target_traits::range_type, typename source_traits::value_type>));

            bool t_copy_success = true;
            while (n > 0u && t_copy_success && p_source.move_front()) {
                t_copy_success = p_target.put(p_source.front());
                --n;
            }

            return t_copy_success;
        }
    };

    struct one_pass_range : trivial_range {
        template<class TRange, class URange>
        static void are_equivalent(TRange && p_l, URange && p_r)
        {
            bool t_both_non_empty = p_l.move_front() && p_r.move_front();
            bool t_both_empty = t_both_non_empty ? false : (!p_l.move_front() && !p_r.move_front());
            Assert::IsTrue((t_both_non_empty != t_both_empty) && (t_both_empty || t_both_non_empty));
            
            if (t_both_non_empty) do {
                Assert::AreEqual(p_l.front(), p_r.front());
            } while (p_l.move_front() && p_r.move_front());

            Assert::IsTrue(!p_l.move_front() && !p_r.move_front());
        }

        template<class TItr, class TRange>
        static void are_equivalent(TItr p_beg, TItr p_end, TRange && p_range)
        {
            Assert::IsTrue(
                p_beg == p_end && !p_range.move_front() ||
                p_beg != p_end && p_range.move_front());

            for (; p_beg != p_end; ++p_beg) {
                Assert::AreEqual(*p_beg, p_range.front());
                p_range.move_front();
            }

            Assert::IsTrue(p_beg == p_end && !p_range.move_front());
        }

        template<class TContainer, class UContainer, class TRange, class URange>
        static void supports_and_executes_swap_correctly(
            TContainer const & p_cont1, UContainer const & p_cont2, 
            TRange& p_range1, URange& p_range2)
        {
            //BOOST_CONCEPT_ASSERT((boost::ForwardIterator<typename TContainer::iterator>));
            //BOOST_CONCEPT_ASSERT((boost::ForwardIterator<typename UContainer::iterator>));
            //BOOST_CONCEPT_ASSERT((boost::ForwardContainer<TContainer>));
            //BOOST_CONCEPT_ASSERT((boost::ForwardContainer<UContainer>));
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::OnePassRange<TRange>));
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::OnePassRange<URange>));

            // reference containers are not empty
            Assert::IsTrue(std::distance(p_cont1.begin(), p_cont1.end()) >= 2u, wlog_message()
                << "expected distance >= 2, measured = " << std::distance(p_cont1.begin(), p_cont1.end()) | as_string);
            Assert::IsTrue(std::distance(p_cont2.begin(), p_cont2.end()) >= 2u, wlog_message()
                << "expected distance >= 2, measured = " << std::distance(p_cont1.begin(), p_cont1.end()) | as_string);

            // ranges are ready to use:
            Assert::IsTrue(p_range1.move_front());
            Assert::IsTrue(p_range2.move_front());

            // fronts and backs are equal:
            Assert::AreEqual(p_cont1.front(), p_range1.front());
            Assert::AreEqual(p_cont2.front(), p_range2.front());
            //Assert::AreEqual(p_cont1.back(), p_range1.back());
            //Assert::AreEqual(p_cont2.back(), p_range2.back());

            // swap ranges
            p_range1.swap(p_range2);

            // fronts and backs of swapped ranges are equal:
            Assert::AreEqual(p_cont1.front(), p_range2.front());
            Assert::AreEqual(p_cont2.front(), p_range1.front());
            //Assert::AreEqual(p_cont1.back(), p_range2.back());
            //Assert::AreEqual(p_cont2.back(), p_range1.back());

            auto t_beg1 = p_cont1.begin(); ++t_beg1;
            auto t_beg2 = p_cont2.begin(); ++t_beg2;

            are_equivalent(t_beg2, p_cont2.end(), p_range1);
            are_equivalent(t_beg1, p_cont1.end(), p_range2);
        }

        template<class TRange, class TRefItr>
        static void move_front_succeeds_n_times_on_range_with_n_elements(
            TRange && p_range, TRefItr p_ref_beg, TRefItr p_ref_end)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::OnePassRange<typename std::decay<TRange>::type>));
            BOOST_CONCEPT_ASSERT((boost::ForwardIterator<typename std::decay<TRefItr>::type>));

            std::size_t t_ref_size = std::distance(p_ref_beg, p_ref_end);
            auto t_ref_front = p_ref_beg;
            std::size_t t_size = 0u;
            while (p_range.move_front()) {
                ++t_size;
                Assert::AreEqual(*t_ref_front++, p_range.front());
            }
            Assert::IsFalse(p_range.move_front());
            Assert::AreEqual(t_ref_size, t_size);
            Assert::IsTrue(t_ref_front == p_ref_end);
        }

        template<class TRange, class TRefItr>
        static void after_calling_move_front_n_times_front_points_to_nth_element(
            TRange && p_range, std::size_t p_n, TRefItr p_ref_beg, TRefItr p_ref_end)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::OnePassRange<typename std::decay<TRange>::type>));
            BOOST_CONCEPT_ASSERT((boost::ForwardIterator<typename std::decay<TRefItr>::type>));

            Assert::IsTrue(0u < p_n && p_n < std::numeric_limits<std::size_t>::max(), wlog_message() <<
                "n must be greater 0 you passed " << p_n | as_string);
            Assert::IsTrue(std::distance(p_ref_beg, p_ref_end) >= std::ptrdiff_t(p_n));

            std::advance(p_ref_beg, p_n - 1u);
            while (p_n > 0u) {
                Assert::IsTrue(p_range.move_front());
                --p_n;
            }
            Assert::AreEqual(std::size_t(0u), p_n);
            Assert::AreEqual(*p_ref_beg, p_range.front());
        }
    };

    struct forward_range : public one_pass_range {
        template <class TRange>
        static void save_creates_a_copy_of_current_range(TRange && p_range)
        { 
            typedef vex::iterable::range_traits<TRange> traits;
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::ForwardRange<typename traits::range_type>));

            std::vector<typename traits::value_type> t_values;
            auto t_saved_range = p_range.save();

            // consume p_range
            while (p_range.move_front()) {
                t_values.push_back(p_range.front());
            }

            // compare t_saved_range to t_values
            are_equivalent(t_values.begin(), t_values.end(), t_saved_range);
        }
    };

    struct bidirectional_range : public forward_range {
        template<class TRange, class TRefItr>
        static void move_back_succeeds_n_times_on_range_with_n_elements(
            TRange && p_range, TRefItr p_ref_beg, TRefItr p_ref_end)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<typename std::decay<TRange>::type>));
            BOOST_CONCEPT_ASSERT((boost::ForwardIterator<typename std::decay<TRefItr>::type>));

            std::size_t t_ref_size = std::distance(p_ref_beg, p_ref_end);
            std::size_t t_size = 0u;
            while (p_range.move_back()) {
                ++t_size;
                Assert::AreEqual(*p_ref_beg++, p_range.back());
            }
            Assert::IsFalse(p_range.move_back());
            Assert::AreEqual(t_size, t_ref_size);
            Assert::IsTrue(p_ref_beg == p_ref_end);
        }

        template<class TRange, class TRefItr>
        static void after_calling_move_back_n_times_back_points_to_nth_element_from_back(
            TRange && p_range, std::size_t p_n, TRefItr p_ref_beg, TRefItr p_ref_end)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<typename std::decay<TRange>::type>));
            BOOST_CONCEPT_ASSERT((boost::ForwardIterator<typename std::decay<TRefItr>::type>));

            Assert::IsTrue(0u < p_n && p_n < std::numeric_limits<std::size_t>::max(), wlog_message() <<
                "n must be greater 0 you passed " << p_n | as_string);
            Assert::IsTrue(std::distance(p_ref_beg, p_ref_end) >= std::ptrdiff_t(p_n));

            std::advance(p_ref_beg, p_n - 1u);
            while (p_n > 0u) {
                Assert::IsTrue(p_range.move_back());
                --p_n;
            }
            Assert::AreEqual(std::size_t(0u), p_n);
            Assert::AreEqual(*p_ref_beg, p_range.back());
        }

        template<class TRange>
        static void either_move_front_or_move_back_are_possible_on_range_with_one_element(
            TRange && p_range_with_one_element)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<typename std::decay<TRange>::type>));

            {
                auto r = p_range_with_one_element.save();
                Assert::IsTrue(r.move_front());
                Assert::IsFalse(r.move_back());
            }

            {
                auto r = p_range_with_one_element.save();
                Assert::IsTrue(r.move_back());
                Assert::IsFalse(r.move_front());
            }
        }

        template<class TRange, class T>
        static void front_and_back_are_equal_on_range_with_one_element(
            TRange && p_range_with_one_element, T const & p_element)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<typename std::decay<TRange>::type>));
            {
                auto r = p_range_with_one_element.save();

                Assert::IsTrue(r.move_front());
                Assert::AreEqual(p_element, r.front());
                Assert::AreEqual(p_element, r.back());
            }

            {
                auto r = p_range_with_one_element.save();

                Assert::IsTrue(r.move_back());
                Assert::AreEqual(p_element, r.back());
                Assert::AreEqual(p_element, r.front());
            }
        }

        template<class TRange, class TRefItr>
        static void front_and_back_meet_in_middle_of_range_with_n_elements(
            TRange && p_range, TRefItr p_beg, TRefItr p_end)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<typename std::decay<TRange>::type>));
            BOOST_CONCEPT_ASSERT((boost::ForwardIterator<TRefItr>));

            std::size_t n = std::distance(p_beg, p_end), n_half = n/2u;
            std::advance(p_beg, n_half);

            while (p_range.move_front() && p_range.front() != *p_beg);
            while (p_range.back() != *p_beg && p_range.move_back());

            Assert::AreEqual(p_range.front(), p_range.back());
            Assert::IsFalse(p_range.move_front());
            Assert::IsFalse(p_range.move_back());
        }

    };

    struct random_access_range : public bidirectional_range {
        template<class TRandomAccessRange>
        static void size_always_has_expected_value(TRandomAccessRange && p_range, std::size_t p_exp_size)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));
            Assert::AreEqual(p_exp_size, p_range.size());
        }

        template<class TRandomAccessRange>
        static void each_subsequent_move_front_decreases_size_by_one(TRandomAccessRange && p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            auto t_size = p_range.size();
            while (p_range.move_front()) {
                Assert::AreEqual(t_size--, p_range.size());
            }
            Assert::AreEqual(std::size_t(0u), t_size);
        }

        template<class TRandomAccessRange>
        static void each_subsequent_move_back_decreases_size_by_one(TRandomAccessRange && p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            auto t_size = p_range.size();
            while (p_range.move_back()) {
                Assert::AreEqual(t_size--, p_range.size());
            }
            Assert::AreEqual(std::size_t(0u), t_size);
        }

        template<class TRandomAccessRange>
        static void each_subsequent_move_front_of_decreases_size_by_one(TRandomAccessRange && p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            auto t_size = p_range.size();
            while (p_range.move_front_of(1u)) {
                Assert::AreEqual(t_size--, p_range.size());
            }
            Assert::AreEqual(std::size_t(0u), t_size);
        }

        template<class TRandomAccessRange>
        static void each_subsequent_move_back_of_decreases_size_by_one(TRandomAccessRange && p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            auto t_size = p_range.size();
            while (p_range.move_back_of(1u)) {
                Assert::AreEqual(t_size--, p_range.size());
            }
            Assert::AreEqual(std::size_t(0u), t_size);
        }

        template<class TRandomAccessRange, class TIndexItr, class TValueItr>
        static void at_and_size_are_available_on_not_started_range(TRandomAccessRange&& p_range,
            TIndexItr p_index_beg, TIndexItr p_index_end, TValueItr p_value_beg)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));
            BOOST_CONCEPT_ASSERT((boost::InputIterator<TIndexItr>));
            BOOST_CONCEPT_ASSERT((boost::InputIterator<TValueItr>));
            auto t_size = p_range.size();
            for (; p_index_beg != p_index_end; ++p_index_beg) {
                Assert::IsTrue(*p_index_beg < t_size);
                Assert::AreEqual(*p_value_beg++, p_range.at(*p_index_beg));
            }
        }

        template<class TRandomAccessRange>
        static void front_always_equals_at_of_zero_in_non_empty_range(
            TRandomAccessRange&& p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));
            while (p_range.move_front()) {
                Assert::AreEqual(p_range.at(0), p_range.front());
            }
        }

        template<class TRandomAccessRange>
        static void back_always_equals_at_of_size_minus_one_in_non_empty_range(
            TRandomAccessRange&& p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            while (p_range.move_back()) {
                Assert::AreEqual(p_range.at(p_range.size() - 1u), p_range.back());
            }
        }

        template<class TRandomAccessRange>
        static void move_front_of_n_has_the_same_effect_as_calling_move_front_n_times(
            TRandomAccessRange&& p_range, std::size_t n)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));
            Assert::IsTrue(0u < n && n < p_range.size(), wlog_message()
                << "n must be greater 0 and less than p_range.size, you passed " << n | as_string);

            auto t_saved_range = p_range.save();
            auto t_n = n;
            while (t_n > 0u) {
                Assert::IsTrue(t_saved_range.move_front());
                --t_n;
            }

            Assert::IsTrue(p_range.move_front_of(n));
            Assert::AreEqual(p_range.front(), t_saved_range.front());
            Assert::AreEqual(p_range.size(), t_saved_range.size());
        }

        template<class TRandomAccessRange>
        static void move_back_of_n_has_the_same_effect_as_calling_move_back_n_times(
            TRandomAccessRange&& p_range, std::size_t n)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            Assert::IsTrue(0u < n && n < p_range.size(), wlog_message()
                << "n must be greater 0 and less than p_range.size, you passed " << n | as_string);

            auto t_saved_range = p_range.save();
            auto t_n = n;
            while (t_n > 0u) {
                Assert::IsTrue(t_saved_range.move_back());
                --t_n;
            }

            Assert::IsTrue(p_range.move_back_of(n));
            Assert::AreEqual(p_range.back(), t_saved_range.back());
            Assert::AreEqual(p_range.size(), t_saved_range.size());
        }

        template<class TRandomAccessRange>
        static void move_front_of_n_returns_false_for_empty_range_and_arbitrary_n(
            TRandomAccessRange&& p_range, std::size_t n)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));
            
            Assert::IsTrue(0u == p_range.size(), wlog_message() 
                << "empty iterable required for this test!" | as_string);

            Assert::IsFalse(p_range.move_front_of(n));
        }

        template<class TRandomAccessRange>
        static void move_back_of_n_returns_false_for_empty_range_and_arbitrary_n(
            TRandomAccessRange&& p_range, std::size_t n)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));
            
            Assert::IsTrue(0u == p_range.size(), wlog_message() 
                << "empty iterable required for this test!" | as_string);

            Assert::IsFalse(p_range.move_back_of(n));
        }

        template<class TRandomAccessRange>
        static void move_front_of_n_returns_false_for_n_equal_0_if_range_is_not_started(
            TRandomAccessRange&& p_not_started_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            Assert::IsFalse(p_not_started_range.move_front_of(0u));
            Assert::IsFalse(p_not_started_range.move_front_of(0u));
            Assert::IsFalse(p_not_started_range.move_front_of(0u));
        }

        template<class TRandomAccessRange>
        static void move_back_of_n_returns_false_for_n_equal_0_if_range_is_not_started(
            TRandomAccessRange&& p_not_started_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            Assert::IsFalse(p_not_started_range.move_back_of(0u));
            Assert::IsFalse(p_not_started_range.move_back_of(0u));
            Assert::IsFalse(p_not_started_range.move_back_of(0u));
        }

        template<class TRandomAccessRange>
        static void move_front_of_n_returns_false_for_n_out_of_bounds(
            TRandomAccessRange&& p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            Assert::IsFalse(p_range.move_front_of(p_range.size() + 1));
            Assert::IsFalse(p_range.move_front_of(-1));
            Assert::IsFalse(p_range.move_front_of(std::numeric_limits<std::size_t>::max()));
        }

        template<class TRandomAccessRange>
        static void move_back_of_n_returns_false_for_n_out_of_bounds(
            TRandomAccessRange&& p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            Assert::IsFalse(p_range.move_back_of(p_range.size() + 1));
            Assert::IsFalse(p_range.move_back_of(-1));
            Assert::IsFalse(p_range.move_back_of(std::numeric_limits<std::size_t>::max()));
        }

        template<class TRandomAccessRange>
        static void move_front_of_n_returns_true_if_n_is_size_and_then_front_is_back_and_size_is_one(
            TRandomAccessRange&& p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            Assert::IsTrue(0u < p_range.size(), wlog_message()
                << "a non-empty iterable required for this test!" | as_string);

            Assert::IsTrue(p_range.move_front_of(p_range.size()));
            Assert::AreEqual(p_range.front(), p_range.back());
            Assert::AreEqual(std::size_t(1u), p_range.size());
        }

        template<class TRandomAccessRange>
        static void move_back_of_n_returns_true_if_n_is_size_and_then_back_is_front_and_size_is_one(
            TRandomAccessRange&& p_range)
        {
            BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<TRandomAccessRange>));

            Assert::IsTrue(0u < p_range.size(), wlog_message()
                << "a non-empty iterable required for this test!" | as_string);

            Assert::IsTrue(p_range.move_back_of(p_range.size()));
            Assert::AreEqual(p_range.back(), p_range.front());
            Assert::AreEqual(std::size_t(1u), p_range.size());
        }
    };
}